package view;

import java.awt.Color;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.Observable;
import java.util.Observer;

import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;

import model.Register;
import controller.MemoryController;
import controller.RuntimeState;

@SuppressWarnings("serial")
public class MemoryView extends JComponent implements Observer, ActionListener {
	private MemoryController memoryController;
	private RegisterView registerView = new RegisterViewString();
	private RuntimeState runtimeState;
	private JTable table = new JTable();

	private String stringString = "String";
	private String stringBinary = "Binary";
	private String stringHex = "Hex";
	private String stringDecimal = "Decimal";

	public MemoryView() {
		this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
		this.add(makeContent());
		memoryController = MemoryController.getInstance();
		memoryController.addObserver(this);
		table.setDefaultRenderer(Object.class, new CustomTableCellRenderer());
	}

	private JComponent makeContent() {
		JComponent panel = new JPanel();
		panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
		JComponent memory = makeMemory();
		memory.setAlignmentX(LEFT_ALIGNMENT);
		panel.add(memory);
		JComponent formatChooser = makeFormatChooser();
		formatChooser.setAlignmentX(LEFT_ALIGNMENT);
		panel.add(formatChooser);
		return panel;
	}

	private JComponent makeMemory() {
		table.setModel(new TableModel(registerView, runtimeState));
		table.getColumnModel().getColumn(0).setPreferredWidth(20);
		table.getColumnModel().getColumn(1).setPreferredWidth(250);
		JScrollPane pane = new JScrollPane(table);
		pane
				.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
		return pane;
	}

	private JComponent makeFormatChooser() {
		JPanel panel = new JPanel();
		panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
		JRadioButton string = new JRadioButton(stringString);
		string.setMnemonic(KeyEvent.VK_S);
		string.setActionCommand(stringString);
		string.setSelected(true);

		JRadioButton binary = new JRadioButton(stringBinary);
		binary.setMnemonic(KeyEvent.VK_B);
		binary.setActionCommand(stringBinary);

		JRadioButton hex = new JRadioButton(stringHex);
		hex.setMnemonic(KeyEvent.VK_H);
		hex.setActionCommand(stringHex);

		JRadioButton decimal = new JRadioButton(stringDecimal);
		decimal.setMnemonic(KeyEvent.VK_D);
		decimal.setActionCommand(stringDecimal);

		ButtonGroup group = new ButtonGroup();
		group.add(string);
		group.add(binary);
		group.add(hex);
		group.add(decimal);

		binary.addActionListener(this);
		hex.addActionListener(this);
		decimal.addActionListener(this);
		string.addActionListener(this);

		panel.add(string);
		panel.add(binary);
		panel.add(hex);
		panel.add(decimal);

		return panel;
	}

	@Override
	public void update(Observable o, Object state) {
		runtimeState = (RuntimeState) state;
		table.setModel(new TableModel(registerView, runtimeState));
		table.getColumnModel().getColumn(0).setPreferredWidth(20);
		table.getColumnModel().getColumn(1).setPreferredWidth(250);
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		if (e.getActionCommand().equals(stringBinary)) {
			registerView = new RegisterViewBinary();
		}
		if (e.getActionCommand().equals(stringHex)) {
			registerView = new RegisterViewHex();
		}
		if (e.getActionCommand().equals(stringDecimal)) {
			registerView = new RegisterViewDecimal();
		}
		if (e.getActionCommand().equals(stringString)) {
			registerView = new RegisterViewString();
		}
		table.setModel(new TableModel(registerView, runtimeState));
		table.getColumnModel().getColumn(0).setPreferredWidth(20);
		table.getColumnModel().getColumn(1).setPreferredWidth(250);
	}

	private class TableModel extends AbstractTableModel {
		private final RegisterView strategy;
		private final RuntimeState runtime;

		public TableModel(RegisterView strategy, RuntimeState data) {

			this.strategy = strategy;
			this.runtime = data;
		}

		private final int ROW_COLUMN = 0;
		private final int DATA_COLUMN = 1;

		public int getRowColumn() {
			return ROW_COLUMN;
		}

		public String getColumnName(int col) {
			if (col == getRowColumn()) {
				return "#";
			} else {
				return strategy.getColumnName();
			}
		}

		public int getRowCount() {
			if (runtime == null) {
				return 0;
			} else if (runtime.getMemory() == null) {
				return 0;
			}

			return runtime.getMemory().size();
		}

		public int getColumnCount() {
			return 2;
		}

		public Object getValueAt(int row, int col) {
			if (col == DATA_COLUMN) {
				int index = 0;
				for (Register reg : runtime.getMemory()) {
					if (index++ == row) {
						return strategy.convertValue(reg);
					}
				}
				return new Integer(0);
			} else {
				return new Integer(row);
			}
		}

		public boolean isCellEditable(int row, int col) {
			return false;
		}

		public void setValueAt(Object value, int row, int col) {
			// nothing to do
		}

		public RuntimeState getRuntimeState() {
			return runtime;
		}
	}

	public class CustomTableCellRenderer extends DefaultTableCellRenderer {
		@Override
		public Component getTableCellRendererComponent(JTable table,
				Object value, boolean isSelected, boolean hasFocus, int row,
				int column) {
			Component cell = super.getTableCellRendererComponent(table, value,
					isSelected, hasFocus, row, column);

			if (!(table.getModel() instanceof TableModel)) {
				return cell;
			}

			TableModel model = (TableModel) table.getModel();

			cell.setBackground(new Color(238, 238, 238));
			if (row == model.getRuntimeState().getPointer()) {
				cell.setBackground(Color.cyan);
			}

			return cell;
		}
	}
}
